import * as THREE from '../../../../modules/three/three.module.js'
import { KeyboardState } from '../CommonConsts.js';
import {
  canvasCoordToScreenCoord,
  createContourLine,
  flatten,
  getMouseRaycaster,
  getOldToNewAngle,
} from '../util/StaticMethods.js';
import AxisArrow from './subset/AxisArrow.js';
import HitTestPlane from './subset/HitTestPlane.js';

const _raycaster = new THREE.Raycaster();
const unitX = new THREE.Vector3(1, 0, 0);
const unitY = new THREE.Vector3(0, 1, 0);
const unitZ = new THREE.Vector3(0, 0, 1);
const alignVector = new THREE.Vector3(0, 0, 1);
const vector2 = new THREE.Vector2();
const normalOnPlane = new THREE.Vector2(0, 1);
const tmpEuler = new THREE.Euler();
const tmpQuat1 = new THREE.Quaternion();
const tmpQuat2 = new THREE.Quaternion();
const _mvPosition = new THREE.Vector3();
const _modelViewMatrix = new THREE.Matrix4();
const matInv = new THREE.Matrix4();
const signVector = new THREE.Vector3(1, 1, 1);
// value 为 true时, 表示正在开始拖动，false表示拖动结束
export const eventDraggingChanged = {
  type: 'dragging-changed',
  value: false,
  rotated: false,
  moved: false,
  clearStatus() {
    this.rotated = false
    this.moved = false
  }
};
export const eventDetached = {
  type: 'control-detached',
};
export const eventRotating = {
  type: 'rotating'
}
export const eventMoving = {
  type: 'moving'
}

// 交互模式
const Mode = {
  NONE: 'NONE',
  TRANSLATE: 'TRANSLATE',
  ROTATE: 'ROTATE',
};

const AXES = {
  X: 'X',
  Y: 'Y',
  Z: 'Z',
  XZ: 'XZ',
  YZ: 'YZ',
  XY: 'XY',
};

function intersectObjectWithRay(object, raycaster, includeInvisible = false) {
  const allIntersections = raycaster.intersectObject(object, true);
  for (let i = 0; i < allIntersections.length; i++) {
    if (allIntersections[i].object.visible || includeInvisible) {
      return allIntersections[i];
    }
  }
  return false;
}

class AxisCylinder extends THREE.Object3D {
  constructor(color, length) {
    super();
    this.length = length;
    const mesh = this._createCylinderMesh(color);
    this.add(mesh);
  }

  _createCylinderMesh(color) {
    const geo = new THREE.CylinderGeometry(0.01, 0.01, this.length, 32);
    const mat = new THREE.MeshBasicMaterial({
      color: color,
      transparent: true,
      depthTest: false,
      toneMapped: false,
    });
    return new THREE.Mesh(geo, mat);
  }
}

class RotatePointer extends THREE.Object3D {
  constructor(radius, triHeight, color = '#ff6000') {
    super();
    const halfAngle = (25.0 / 180) * Math.PI;
    const hw = Math.tan(halfAngle) * triHeight;
    const p0 = new THREE.Vector3(0, triHeight + radius, 0);
    const p1 = new THREE.Vector3(hw, radius, 0);
    const p2 = new THREE.Vector3(-hw, radius, 0);
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute(
      'position',
      new THREE.BufferAttribute(flatten([p0, p1, p2]), 3),
    );
    geometry.setIndex([0, 1, 2]);
    const material = new THREE.MeshBasicMaterial({
      side: THREE.DoubleSide,
      color: color,
      transparent: true,
      opacity: 1.0,
      depthTest: false,
      toneMapped: false,
    });
    const triMesh = new THREE.Mesh(geometry, material);
    this.add(triMesh);
  }
}
/**
 * 绘制带刻度的 2d 圆环
 */
class CirceRing extends THREE.Object3D {
  constructor(
    innerRadius,
    ringThickness,
    color = '#a239d7',
    lineColor = '#54c058',
    solid = false,
    pieNum = 8,
    segments = 64,
  ) {
    super();
    const outerRadius = innerRadius + ringThickness;
    let curAngle = 0;
    let angle = (Math.PI * 2) / segments;
    // 绘制圆环
    if (solid) {
      const ring = new PieRing(
        innerRadius,
        ringThickness,
        Math.PI * 2,
        segments,
        color,
      );
      ring.children[0].material.opacity = 0.8;
      ring.children.forEach((mesh) => {
        this.add(mesh);
        mesh.renderOrder = 4;
      });
    }
    const circlePoints1 = [];
    const circlePoints2 = [];
    for (let i = 0; i < segments; i++) {
      const point = new THREE.Vector3(0, 0, 0);
      point.y = Math.sin(curAngle);
      point.x = Math.cos(curAngle);
      circlePoints1.push(point.clone().multiplyScalar(innerRadius));
      circlePoints2.push(point.clone().multiplyScalar(outerRadius));
      curAngle += angle;
    }
    const circleLineSegment1 = createContourLine(circlePoints1, 0);
    circleLineSegment1.material.color.setStyle(lineColor);
    this.add(circleLineSegment1);
    circleLineSegment1.renderOrder = 6;
    const circleLineSegment2 = createContourLine(circlePoints2, 0);
    circleLineSegment2.material.color.setStyle(lineColor);
    this.add(circleLineSegment2);
    circleLineSegment2.renderOrder = 6;

    // 绘制刻度
    const vectors = [];
    const lineSegmentVectors = [];
    curAngle = 0;
    angle = (Math.PI * 2) / pieNum;
    for (let i = 0; i < pieNum; i++) {
      const point = new THREE.Vector3(0, 0, 0);
      point.y = Math.sin(curAngle);
      point.x = Math.cos(curAngle);
      vectors.push(point);
      curAngle += angle;
      lineSegmentVectors.push(
        point.clone().multiplyScalar(innerRadius),
        point.clone().multiplyScalar(outerRadius),
      );
    }
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute(
      'position',
      new THREE.BufferAttribute(flatten(lineSegmentVectors), 3),
    );
    const material = new THREE.LineBasicMaterial({
      color: lineColor,
      depthTest: false,
      transparent: true,
    });
    const lineSegments = new THREE.LineSegments(geometry, material);
    lineSegments.renderOrder = 6;
    this.add(lineSegments);
  }
}

/**
 * 创建扇形圆环和 圆柱侧面的一部分，默认四分之一圆
 */
class PieRing extends THREE.Object3D {
  colorWorld = '#1e68c5'
  colorLocal = '#de19d4'
  meshPie

  constructor(
    innerRadius,
    ringThickness,
    pieAngle = Math.PI * 0.5,
    segments = 16,
    color = '#1e68c5',
  ) {
    super();
    this.colorWorld = color
    this.innerRadius = innerRadius;
    this.thickness = ringThickness;
    const outerRadius = innerRadius + ringThickness;
    const midRadius = innerRadius + 0.5 * ringThickness;
    const angle = pieAngle / segments;
    const vectors = [];
    let curAngle = 0;
    for (let i = 0; i <= segments; i++) {
      const point = new THREE.Vector3(0, 0, 0);
      point.y = Math.sin(curAngle);
      point.x = Math.cos(curAngle);
      vectors.push(point);
      curAngle += angle;
    }
    const innerVectors = vectors.map((vector) =>
      vector.clone().multiplyScalar(innerRadius),
    );
    const outerVectors = vectors.map((vector) =>
      vector.clone().multiplyScalar(outerRadius),
    );
    const lineIndices = [];
    const indices = [];
    //   i + len ────────────── i + 1 + len─────
    //     │                       │
    //     │                       │
    //     i  ───────────────────i + 1────
    const len = segments + 1;
    for (let i = 0; i < len - 1; i++) {
      indices.push(i, i + 1, i + 1 + len);
      indices.push(i, i + 1 + len, i + len);
      lineIndices.push(i, i + 1)
      lineIndices.push(i + len, i + len + 1)
    }
    lineIndices.push(0, len, len - 1, 2 * len - 1)
    const geometryPie = new THREE.BufferGeometry();
    const posAttr = new THREE.BufferAttribute(flatten(innerVectors.concat(outerVectors)), 3)
    geometryPie.setAttribute('position', posAttr);
    geometryPie.setIndex(indices);
    const geoContour = new THREE.BufferGeometry();
    geoContour.setAttribute('position', posAttr)
    geoContour.setIndex(lineIndices)
    const lineContour = new THREE.LineSegments(geoContour, new THREE.LineBasicMaterial({ color}))

    // const midVectors = vectors.map(vector => vector.clone().multiplyScalar(midRadius))
    // const topVectors = midVectors.map(vector => new THREE.Vector3(vector.x, vector.y, ringThickness * 0.5))
    // const bottomVectors = midVectors.map(vector => new THREE.Vector3(vector.x, vector.y, -ringThickness * 0.5))
    // const geometryCylinder = new THREE.BufferGeometry()
    // geometryCylinder.setAttribute('position', vectorsToBufferAttribute(bottomVectors.concat(topVectors)))
    // geometryCylinder.setIndex(indices)
    const mat = new THREE.MeshBasicMaterial({
      side: THREE.DoubleSide,
      color: color,
      transparent: true,
      opacity: 0.4,
      depthTest: false,
    });
    const meshPie = new THREE.Mesh(geometryPie, mat);
    meshPie.renderOrder = 5;
    meshPie.name = 'PieRing';
    this.add(meshPie);
    this.meshPie = meshPie
    meshPie.add(lineContour)
    // const meshCylinderSide = new THREE.Mesh(geometryCylinder, mat)
    // this.add(meshCylinderSide)
  }

  toggleLocal(isLocal) {
    const color = isLocal ? this.colorLocal : this.colorWorld
    this.meshPie.material.color.setStyle(color)
  }
}

export default class TransformRotateControl extends THREE.Object3D {
  /**
   * @type {THREE.Camera}
   */
  camera;
  /**
   * 用于把 零变换空间的位置或方向 转换为 this.object.parent 空间中的位置或方向
   * @type {THREE.Matrix4}
   * @private
   */
  _objParentInvMat = new THREE.Matrix4();
  /**
   * 用于把 this.object.parent 空间中的位置或方向，转换为零变换空间的位置或方向
   * @type {THREE.Matrix4}
   * @private
   */
  _objParentMat = new THREE.Matrix4();
  /**
   * 可见对象
   * @type {THREE.Object3D[]}
   * @private
   */
  _visibleItems = [];
  /**
   * 可以被射线撞击的网格
   * @type {THREE.Mesh[]}
   * @private
   */
  handlers = [];
  _moveFromLocalSpace = false;
  _enableMove = true

  constructor(camera, domEle) {
    super();
    this.visible = false;
    this.camera = camera;
    this.domElement = domEle || document;
    // this.domElement.style.touchAction = 'none'; // disable touch scroll

    this.object = undefined;
    const colorX = '#f00';
    const colorY = '#0f0';
    const colorZ = '#00f';

    this._planeXY = new HitTestPlane(colorZ);
    this._planeXY.userData.normal = unitZ;
    this.add(this._planeXY);

    this._planeXZ = new HitTestPlane(colorY);
    this._planeXZ.userData.normal = unitY;
    this.add(this._planeXZ);
    this._planeXZ.rotation.x = Math.PI * 0.5;
    this._planeYZ = new HitTestPlane(colorX);
    this._planeYZ.userData.normal = unitX;
    this.add(this._planeYZ);
    this._planeYZ.rotation.y = Math.PI * 0.5;

    this._hitPlane = undefined;
    const arr = [this._planeXY, this._planeXZ, this._planeYZ];
    arr.forEach((plane) => {
      plane.updateMatrix();
      plane.geometry.applyMatrix4(plane.matrix);
      plane.rotation.set(0, 0, 0);
    });

    const axisLen = 0.3;
    const offset = axisLen * 0.5;
    const axisX = new AxisCylinder(colorX, axisLen);
    axisX.position.x = offset;
    axisX.rotation.z = Math.PI * 0.5;
    axisX.name = 'X';
    const axisY = new AxisCylinder(colorY, axisLen);
    axisY.position.y = offset;
    axisY.name = 'Y';
    const axisZ = new AxisCylinder(colorZ, axisLen);
    axisZ.position.z = offset;
    axisZ.rotation.x = Math.PI * 0.5;
    axisZ.name = 'Z';
    this.add(axisX);
    this.add(axisY);
    this.add(axisZ);
    const arrowSize = 0.3;
    const arrowX = new AxisArrow(colorX, arrowSize);
    const arrowY = new AxisArrow(colorY, arrowSize);
    const arrowZ = new AxisArrow(colorZ, arrowSize);
    arrowX.name = AXES.X + ' arrow';
    arrowY.name = AXES.Y + ' arrow';
    arrowZ.name = AXES.Z + ' arrow';
    const radiusArrow = 1.1;
    this.add(arrowX);
    arrowX.position.x = radiusArrow;
    arrowX.rotation.z = Math.PI * -0.5;
    this.add(arrowY);
    arrowY.position.y = radiusArrow;
    this.add(arrowZ);
    arrowZ.position.z = radiusArrow;
    arrowZ.rotation.x = Math.PI * 0.5;

    let radiusInner = 0.55;
    let thickness = 0.15;
    let gap = 0.022;
    // 鼠标 拖拽 该四分之一圆环时，表示需要绕 z 轴旋转物体
    const pieXY = new PieRing(radiusInner, thickness);
    this.add(pieXY);
    pieXY.position.set(gap, gap, 0);
    pieXY.name = AXES.Z + ' XY';

    const pieXZ = new PieRing(radiusInner, thickness);
    pieXZ.name = AXES.Y + ' XZ';
    this.add(pieXZ);
    pieXZ.position.set(gap, 0, gap);
    pieXZ.rotation.x = Math.PI * 0.5;

    const pieYZ = new PieRing(radiusInner, thickness);
    pieYZ.name = AXES.X + ' YZ';
    this.add(pieYZ);
    pieYZ.position.set(0, gap, gap);
    pieYZ.rotation.y = -Math.PI * 0.5;

    this._visibleItems = [
      axisX,
      axisY,
      axisZ,
      arrowX,
      arrowY,
      arrowZ,
      pieXY,
      pieXZ,
      pieYZ,
    ];
    this.handlers = [];
    const list = [arrowX, arrowY, arrowZ, pieXY, pieXZ, pieYZ];
    list.forEach((it) => {
      this.handlers.push(it.children[0]);
    });

    this._initRotateSnaps();
    radiusInner = 0.95;
    thickness = 0.28;
    const outterRingColor = '#0f0';
    const outterSegmentColor = '#ffbc11';
    let ringRadius = radiusInner;
    const ringXY = new CirceRing(
      ringRadius,
      thickness,
      outterRingColor,
      outterSegmentColor,
      true,
      this.pieNum,
    );
    const ringXZ = new CirceRing(
      ringRadius,
      thickness,
      outterRingColor,
      outterSegmentColor,
      true,
      this.pieNum,
    );
    ringXZ.rotation.x = Math.PI * 0.5;
    const ringYZ = new CirceRing(
      ringRadius,
      thickness,
      outterRingColor,
      outterSegmentColor,
      true,
      this.pieNum,
    );
    ringYZ.rotation.y = Math.PI * 0.5;
    this.add(ringXY);
    this.add(ringXZ);
    this.add(ringYZ);

    const th = thickness;
    ringRadius = radiusInner - th;
    const innerRingXY = new RotatePointer(ringRadius, th, '#f00');
    const innerRingXZ = new RotatePointer(ringRadius, th);
    const innerRingYZ = new RotatePointer(ringRadius, th);
    innerRingXZ.rotation.x = Math.PI * 0.5;
    innerRingYZ.rotation.y = Math.PI * 0.5;
    this.add(innerRingXY);
    this.add(innerRingXZ);
    this.add(innerRingYZ);
    const hidden = [
      ringXY,
      ringXZ,
      ringYZ,
      innerRingXY,
      innerRingXZ,
      innerRingYZ,
    ];
    hidden.forEach((it) => {
      it.visible = false;
    });
    this.hiddenRings = hidden;
    this.children.forEach((obj3d) => {
      obj3d.updateMatrix();
      obj3d.children.forEach((it) => {
        it.geometry.applyMatrix4(obj3d.matrix);
        if (it.parent.name) {
          it.name = it.parent.name;
        }
      });
      obj3d.position.set(0, 0, 0);
      obj3d.rotation.set(0, 0, 0);
      obj3d.scale.set(1, 1, 1);
    });

    this.visibleSubMesh = [];
    this.ringsMap = new Map();
    this.ringsMap.set(AXES.X, [ringYZ, innerRingYZ]);
    this.ringsMap.set(AXES.Y, [ringXZ, innerRingXZ]);
    this.ringsMap.set(AXES.Z, [ringXY, innerRingXY]);

    this.cameraPosition = new THREE.Vector3();
    // scene空间的坐标
    this.eye = new THREE.Vector3();
    this.dragging = false;
    this.highlighting = false;
    this.mode = Mode.NONE;
    this.axis = AXES.X;

    this._startDir = new THREE.Vector3();
    this._endDir = new THREE.Vector3();
    this._startV2 = new THREE.Vector2();
    this._endV2 = new THREE.Vector2();
    // object对象的父级坐标系下的坐标
    this._positionStart = new THREE.Vector3();
    // object对象的父级坐标系下的坐标
    this._dragStart = new THREE.Vector3();
    // object对象的父级坐标系下的坐标
    this._offset = new THREE.Vector3();
    // object对象的零变化坐标系下的拖动开始坐标
    this._dragStartWorld = new THREE.Vector3();
    this._rotateStart = 0;
    this._activeRotateRing = null; // 当前激活的旋转环

    this._onDragStart = onDragStart.bind(this);
    this._onDragging = onDragging.bind(this);
    this._onDragEnd = onDragEnd.bind(this);
    this.domElement.addEventListener('pointerdown', this._onDragStart);
    this.domElement.addEventListener('pointermove', this._onDragging);
    this.domElement.addEventListener('pointerup', this._onDragEnd);
  }

  dispose() {
    this.domElement.removeEventListener('pointerdown', this._onDragStart);
    this.domElement.removeEventListener('pointermove', this._onDragging);
    this.domElement.removeEventListener('pointerup', this._onDragEnd);
    this.traverse((child) => {
      if (child.geometry) child.geometry.dispose();
      if (child.material) child.material.dispose();
    });
    this.removeFromParent();
  }

  // 显示单个位移箭头
  _showSingleArrowOnly() {
    if (
      this.axis === AXES.XY ||
      this.axis === AXES.XZ ||
      this.axis === AXES.YZ
    ) {
      return;
    }
    this._visibleItems.forEach((mesh) => {
      if (mesh.name.search(this.axis) !== -1 && mesh instanceof AxisArrow) {
        mesh.visible = true;
      } else {
        mesh.visible = false;
      }
    });
  }

  // 取消显示单个位移箭头
  _unshowSingleArrowOnly() {
    if (
      this.axis === AXES.XY ||
      this.axis === AXES.XZ ||
      this.axis === AXES.YZ
    ) {
      return;
    }
    if (this.mode !== Mode.TRANSLATE) {
      return;
    }
    this._visibleItems.forEach((mesh) => {
      mesh.visible = true;
    });
  }

  _initRotateSnaps() {
    const thresholdDegree = 4;
    this._snapThreshold = (thresholdDegree * Math.PI) / 180;
    this._rotationSnapTargets = [];
    const pieNum = 12;
    this.pieNum = pieNum;
    const radian = (Math.PI * 2) / pieNum;
    let cur = -Math.PI;
    for (let i = 0; i <= pieNum; i++) {
      this._rotationSnapTargets.push(cur);
      cur += radian;
    }
  }
  _rotateSnap(radian) {
    if (KeyboardState.keyDownAlt) {
      return rotateSnap(this._rotationSnapTargets, this._snapThreshold, radian);
    }
    return radian;
  }

  // 选择射线撞击的几何平面
  _selectHitTestPlane() {
    this._hitPlane = this._planeXY;
    let planes = [];
    if (this.mode === Mode.TRANSLATE) {
      switch (this.axis) {
        case AXES.X:
          planes = [this._planeXZ, this._planeXY];
          break;
        case AXES.Y:
          planes = [this._planeXY, this._planeYZ];
          break;
        case AXES.Z:
          planes = [this._planeXZ, this._planeYZ];
          break;
        default:
          return;
      }
      const [dot0, dot1] = planes.map((plane) =>
        Math.abs(plane.userData.normal.dot(this.eye)),
      );
      // 数量积越大，和相机的视线角度越小
      if (dot0 > dot1) {
        this._hitPlane = planes[0];
      } else {
        this._hitPlane = planes[1];
      }
    } else if (this.mode === Mode.ROTATE) {
      switch (this.axis) {
        case AXES.X:
          this._hitPlane = this._planeYZ;
          break;
        case AXES.Y:
          this._hitPlane = this._planeXZ;
          break;
        case AXES.Z:
          this._hitPlane = this._planeXY;
          break;
        default:
          break;
      }
    }
  }

  // 修改this._offset，确保物体只在单轴移动，或者只在xy平面移动
  _moveAccordAxes() {
    let offsetLength = 1;
    if (
      this.object.parent &&
      !(this.object.parent instanceof THREE.Scene) &&
      !this._moveFromLocalSpace
    ) {
      // 父级就是scene，不需要变换; this._offset 先转为零变换的世界坐标，在转为scene中的坐标
      offsetLength = this._offset.length();
      this._offset.transformDirection(this._objParentMat);
    } else if (this.getMoveFromLocalSpace()) {
      tmpQuat2.copy(this.quaternion).invert();
      this._offset.applyQuaternion(tmpQuat2);
    }

    switch (this.axis) {
      case AXES.X:
        this._offset.y = 0;
        this._offset.z = 0;
        break;
      case AXES.Y:
        this._offset.x = 0;
        this._offset.z = 0;
        break;
      case AXES.Z:
        this._offset.x = 0;
        this._offset.y = 0;
        break;
      case AXES.XY:
        this._offset.z = 0;
        break;
      default:
        break;
    }
    if (this.object.parent && !(this.object.parent instanceof THREE.Scene)) {
      // 父级就是scene，不需要变换; this._offset 从scene中的坐标，转为this.object.parent 中的坐标
      this._offset
        .transformDirection(this._objParentInvMat)
        .multiplyScalar(offsetLength);
    } else if (this.getMoveFromLocalSpace()) {
      this._offset.applyQuaternion(this.quaternion);
    }
  }

  // 鼠标悬停在 子mesh上，高亮此子mesh
  _highlightHandler(event) {
    const pointer = canvasCoordToScreenCoord(
      {
        x: event.clientX,
        y: event.clientY,
      },
      event.target.getBoundingClientRect(),
    );
    _raycaster.setFromCamera(pointer, this.camera);
    this.highlighting = false;
    if (!this.dragging) {
      this.handlers.forEach((mesh) => (mesh.material.opacity = 0.4));
      const intersects = _raycaster.intersectObjects(this.handlers, false);
      if (!intersects.length) {
        return;
      }
      intersects[0].object.material.opacity = 1.0;
      this.highlighting = true;
    }
  }

  updateMatrixWorld(force) {
    if (!this.object) {
      return;
    }
    this.position.setFromMatrixPosition(this.object.matrixWorld);
    this.cameraPosition.copy(this.camera.position);
    const eye = this.eye;
    eye.copy(this.cameraPosition).sub(this.object.position).normalize();
    if (this.getMoveFromLocalSpace()) {
      this.object.getWorldQuaternion(tmpQuat2);
      tmpQuat2.invert();
      eye.applyQuaternion(tmpQuat2);
    }

    let factor;
    if (this.camera.isOrthographicCamera) {
      factor = (this.camera.top - this.camera.bottom) / this.camera.zoom;
    } else {
      _modelViewMatrix.multiplyMatrices(
        this.camera.matrixWorldInverse,
        this.object.matrixWorld,
      );
      _mvPosition.setFromMatrixPosition(_modelViewMatrix);
      factor = (-_mvPosition.z * this.camera.fov) / 120;
    }
    this.hiddenRings.forEach((ring) => {
      ring.children.forEach((it) =>
        it.scale.set(1, 1, 1).multiplyScalar(factor * 0.25),
      );
    });
    // Flip translate and scale axis ocluded behind another axis
    const AXIS_FLIP_TRESHOLD = 0.0;
    this.handlers.forEach((mesh) => {
      if (this.dragging) {
        mesh.scale.copy(signVector).multiplyScalar(factor * 0.25);
        return;
      } else {
        mesh.scale.set(1, 1, 1).multiplyScalar(factor * 0.25);
      }
      if (mesh.name.search('X') !== -1) {
        signVector.x = 1;
        if (alignVector.copy(unitX).dot(eye) < AXIS_FLIP_TRESHOLD) {
          mesh.scale.x *= -1;
          signVector.x = -1;
        }
        if (mesh.name.search('arrow') !== -1) {
          vector2.set(eye.y, eye.z).normalize();
          mesh.rotation.x = getOldToNewAngle(normalOnPlane, vector2);
        }
      }
      if (mesh.name.search('Y') !== -1) {
        signVector.y = 1;
        if (alignVector.copy(unitY).dot(eye) < AXIS_FLIP_TRESHOLD) {
          mesh.scale.y *= -1;
          signVector.y = -1;
        }
        if (mesh.name.search('arrow') !== -1) {
          vector2.set(eye.x, eye.z).normalize();
          mesh.rotation.y = -getOldToNewAngle(normalOnPlane, vector2);
        }
      }
      if (mesh.name.search('Z') !== -1) {
        signVector.z = 1;
        if (alignVector.copy(unitZ).dot(eye) < AXIS_FLIP_TRESHOLD) {
          mesh.scale.z *= -1;
          signVector.z = -1;
        }
        if (mesh.name.search('arrow') !== -1) {
          vector2.set(eye.x, eye.y).normalize();
          mesh.rotation.z = getOldToNewAngle(normalOnPlane, vector2);
        }
      }
    });
    super.updateMatrixWorld(force);
  }

  /**
   *
   * @param {THREE.Object3D} object
   * @return {TransformRotateControl}
   */
  attach(object) {
    this.object = object;
    this.object.updateMatrixWorld();
    this.position.setFromMatrixPosition(this.object.matrixWorld);
    this._objParentInvMat.copy(object.parent.matrixWorld).invert();
    this._objParentMat.copy(object.parent.matrixWorld);
    this.visible = true;
    this.setMoveFromLocalSpace(this._moveFromLocalSpace);
    return this;
  }

  detach() {
    this.dispatchEvent(eventDetached)
    this.object = undefined;
    this.visible = false;
    this.highlighting = false;
    return this;
  }

  /**
   * true 表示沿着物体旋转后的方向移动
   * @param {boolean} value
   */
  setMoveFromLocalSpace(value) {
    if (this.object) {
      this._moveFromLocalSpace = value;
      if (value) {
        this.object.getWorldQuaternion(tmpQuat2);
      } else {
        tmpQuat2.identity();
      }
      this.quaternion.copy(tmpQuat2);
      this._visibleItems.forEach(obj => {
        if (obj instanceof PieRing) {
          obj.toggleLocal(value)
        }
      })
    }
  }

  getMoveFromLocalSpace() {
    return this._moveFromLocalSpace;
  }

  // 只允许 绕 Z轴旋转物体
  setRotateZOnly() {
    this.handlers.forEach((mesh) => {
      if (mesh.parent instanceof PieRing) {
        if (!mesh.name.startsWith('Z')) {
          mesh.visible = false;
        }
      }
    });
  }

  // 设置旋转组件的显隐
  setRotateVisible(visible) {
    this.handlers.forEach((mesh) => {
      if (mesh.parent instanceof PieRing) {
        mesh.visible = visible;
      }
    });
  }

  // 设置移动组件的显隐
  setTranslateVisible(visible) {
    this._enableMove = visible;
    this.handlers.forEach((mesh) => {
      if (mesh.parent instanceof AxisArrow) {
        mesh.visible = visible;
      }
    });
  }
}

/**
 * @param {PointerEvent} event
 */
function onDragStart(event) {
  if (!this.object) {
    return;
  }
  const object = this.object;
  this.mode = Mode.NONE;
  getMouseRaycaster(
    _raycaster,
    this.camera,
    event.clientX,
    event.clientY,
    event.target.getBoundingClientRect(),
  );
  const visibleHandlers = this.handlers.filter((it) => it.visible);
  const intersects = _raycaster.intersectObjects(visibleHandlers, false);
  if (intersects.length) {
    const intersectObj = intersects[0].object.parent;
    if (intersectObj instanceof PieRing) {
      this.mode = Mode.ROTATE;
      this.axis = intersectObj.name.split(' ')[0]; // 绕哪个轴旋转
    } else if (intersectObj instanceof AxisArrow) {
      // 正在拖动某个轴的箭头
      this.mode = Mode.TRANSLATE;
      this.axis = intersectObj.name.split(' ')[0]; // 沿着哪个轴位移
      this._showSingleArrowOnly();
    }
  } else {
    if (this._enableMove) {
      const directHit = _raycaster.intersectObject(object);
      if (directHit.length) {
        // 选中物体时，默认在xy平面拖拽物体
        this.mode = Mode.TRANSLATE;
        this.axis = AXES.XY;
      }
    }
  }

  if (this.mode === Mode.NONE) {
    return;
  }
  eventDraggingChanged.clearStatus()
  object.updateMatrixWorld();
  matInv.copy(object.matrix).invert()
  this._selectHitTestPlane();
  const planeIntersect = intersectObjectWithRay(
    this._hitPlane,
    _raycaster,
    true,
  );
  if (planeIntersect) {
    this._dragStartWorld.copy(planeIntersect.point);
    const localPoint = planeIntersect.point
      .clone()
      .applyMatrix4(this._objParentInvMat);
    this._dragStart.copy(localPoint);
    this._startDir.copy(this._dragStart).sub(object.position);
    if (this._moveFromLocalSpace) {
      this._startDir.transformDirection(matInv)
    }
    this._positionStart.copy(object.position);
    this.dragging = true;
    eventDraggingChanged.value = this.dragging;
    this.dispatchEvent(eventDraggingChanged);

    const quaternion = this.object.quaternion;
    tmpQuat1.copy(quaternion);
    if (this.mode === Mode.ROTATE) {
      this.visibleSubMesh = this.handlers.filter((it) => it.visible);
      this.visibleSubMesh.forEach((it) => (it.visible = false));
      const obj3dArr = this.ringsMap.get(this.axis);
      obj3dArr.forEach((obj3d) => {
        obj3d.visible = true;
      });
      this._activeRotateRing = obj3dArr[1];
      tmpEuler.copy(this.object.rotation)
      switch (this.axis) {
        case AXES.X:
          if (!this.getMoveFromLocalSpace()) {
            tmpEuler.setFromQuaternion(quaternion);
          }
          obj3dArr[1].rotation.x = tmpEuler.x;
          this._rotateStart = tmpEuler.x;
          break;
        case AXES.Y:
          if (!this.getMoveFromLocalSpace()) {
            tmpEuler.setFromQuaternion(quaternion, 'YXZ');
          }
          obj3dArr[1].rotation.y = tmpEuler.y;
          this._rotateStart = tmpEuler.y;
          break;
        case AXES.Z:
          if (!this.getMoveFromLocalSpace()) {
            tmpEuler.setFromQuaternion(quaternion, 'ZXY');
          }
          obj3dArr[1].rotation.z = tmpEuler.z;
          this._rotateStart = tmpEuler.z;
          break;
        default:
          break;
      }
    }
  }
}

function onDragging(event) {
  if (!this.object) {
    return;
  }
  this._highlightHandler(event);
  if (!this.dragging || this.mode === Mode.NONE) {
    return;
  }

  const planeIntersect = intersectObjectWithRay(
    this._hitPlane,
    _raycaster,
    true,
  );
  if (!planeIntersect) {
    return;
  }
  const object = this.object;
  const localPosition = planeIntersect.point
    .clone()
    .applyMatrix4(this._objParentInvMat);
  this._offset.copy(localPosition).sub(this._dragStart);
  if (this.mode === Mode.TRANSLATE) {
    this._moveAccordAxes();
    object.position.copy(this._positionStart).add(this._offset);
    this.position.setFromMatrixPosition(this.object.matrixWorld);
    this.dispatchEvent(eventMoving)
  } else if (this.mode === Mode.ROTATE) {
    this._endDir.copy(localPosition).sub(this._positionStart);
    if (this._moveFromLocalSpace) {
      this._endDir.transformDirection(matInv)
    }
    let diff = 0;
    object.quaternion.copy(tmpQuat1);
    switch (this.axis) {
      case AXES.Z:
        this._startV2.copy(this._startDir);
        this._endV2.copy(this._endDir);
        diff = getOldToNewAngle(this._startV2, this._endV2);
        diff = this._rotateSnap(this._rotateStart + diff) - this._rotateStart;
        if (this.getMoveFromLocalSpace()) {
          object.rotateZ(diff)
        } else {
          object.rotateOnWorldAxis(unitZ, diff);
        }
        this._activeRotateRing.rotation.z = this._rotateStart + diff;
        break;
      case AXES.Y:
        this._startV2.set(this._startDir.x, this._startDir.z);
        this._endV2.set(this._endDir.x, this._endDir.z);
        diff = getOldToNewAngle(this._startV2, this._endV2);
        diff = this._rotateSnap(this._rotateStart - diff) - this._rotateStart;
        if (this.getMoveFromLocalSpace()) {
          object.rotateY(diff)
        } else {
          object.rotateOnWorldAxis(unitY, diff);
        }

        this._activeRotateRing.rotation.y = this._rotateStart + diff;
        break;
      case AXES.X:
        this._startV2.set(this._startDir.y, this._startDir.z);
        this._endV2.set(this._endDir.y, this._endDir.z);
        diff = getOldToNewAngle(this._startV2, this._endV2);
        diff = this._rotateSnap(this._rotateStart + diff) - this._rotateStart;
        if (this.getMoveFromLocalSpace()) {
          object.rotateX(diff)
        } else {
          object.rotateOnWorldAxis(unitX, diff);
        }
        this._activeRotateRing.rotation.x = this._rotateStart + diff;
        break;
      default:
        break;
    }
    this.dispatchEvent(eventRotating)
  }
}

function onDragEnd() {
  this.dragging = false;
  eventDraggingChanged.value = this.dragging;
  eventDraggingChanged.rotated = this.mode === Mode.ROTATE;
  eventDraggingChanged.moved = this.mode === Mode.TRANSLATE;
  this.dispatchEvent(eventDraggingChanged);
  this._unshowSingleArrowOnly();
  if (this.mode === Mode.ROTATE) {
    this.hiddenRings.forEach((obj3d) => {
      obj3d.visible = false;
    });
    this.visibleSubMesh.forEach((mesh) => {
      mesh.visible = true;
    });
    if (this.getMoveFromLocalSpace()) {
      this.object.getWorldQuaternion(tmpQuat2);
      this.quaternion.copy(tmpQuat2);
    }
  }
}

/**
 * 旋转吸附，返回一个弧度
 * @param {number[]} snapTargets 弧度，待吸附的目标数值列表
 * @param {number} threshold 弧度，当前弧度转为角度后，和待吸附的目标数值的绝对差值小于这个数值，则吸附
 * @param {number} currentRadian 弧度
 */
function rotateSnap(snapTargets, threshold, currentRadian) {
  let radian = currentRadian;
  while (radian < -Math.PI) {
    radian += Math.PI * 2;
  }
  while (radian > Math.PI) {
    radian -= Math.PI * 2;
  }
  let min = Infinity;
  let index = -1;
  for (let i = 0; i < snapTargets.length; i++) {
    const val = snapTargets[i];
    const diff = Math.abs(val - radian);
    if (diff <= threshold && min > diff) {
      min = diff;
      index = i;
    }
  }
  if (index >= 0) {
    return snapTargets[index];
  }
  return radian; // 没有和任何目标数值吸附
}
